﻿/*********************************************************************************
**                                                                              **
**  Copyright (C) 2024-2025 LiLong                                              **
**  This file is part of OpenVisaApplication.                                   **
**                                                                              **
**  OpenVisaApplication is free software: you can redistribute it and/or modify **
**  it under the terms of the GNU General Public License as published by        **
**  the Free Software Foundation, either version 3 of the License, or           **
**  (at your option) any later version.                                         **
**                                                                              **
**  OpenVisaApplication is distributed in the hope that it will be useful,      **
**  but WITHOUT ANY WARRANTY; without even the implied warranty of              **
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               **
**  GNU General Public License for more details.                                **
**                                                                              **
**  You should have received a copy of the GNU General Public License           **
**  along with OpenVisaApplication. If not, see <https://www.gnu.org/licenses/>.**
**********************************************************************************/
#include "IOTraceService.h"

#include <OpenVisa/Attribute.h>

#include <QCoreApplication>
#include <QNetworkDatagram>
#include <QThread>
#include <QUdpSocket>

namespace IOTrace
{
struct TraceData
{
    QDateTime time;
    bool isTx;
    int currentPackNumber;
    int totalPackNumber;
    QString address;
    QString data;
};

constexpr unsigned short TraceHeader = 0xCFCF;
struct IOTraceService::Impl
{
    unsigned short port { OpenVisa::Object().attribute().ioTracePort() };
    unsigned short version { OpenVisa::Object().attribute().ioTraceVersion() };
    QUdpSocket* udp;
    QThread thread;
    bool stopRequested { false };
    TraceData traceData;
    bool started { false };
};

IOTraceService::IOTraceService() : m_impl(std::make_unique<Impl>())
{
    moveToThread(&m_impl->thread);
    m_impl->thread.start();
    QMetaObject::invokeMethod(this, &IOTraceService::init);
    connect(qApp,
            &QCoreApplication::aboutToQuit,
            qApp,
            [this]
            {
                m_impl->thread.quit();
                m_impl->thread.wait();
            });
}

IOTraceService::~IOTraceService()
{
    m_impl->thread.quit();
    m_impl->thread.wait();
}

void IOTraceService::init() { m_impl->udp = new QUdpSocket(this); }

void IOTraceService::parse(const QByteArray& data)
{
    if (data.size() < 21)
        return;
    size_t offset = 0;
    auto header   = reinterpret_cast<const unsigned short*>(data.constData()); // 2 Bytes
    if (*header != TraceHeader)
        return;
    offset += sizeof(*header);
    auto version = reinterpret_cast<const unsigned short*>(data.constData() + offset); // 2 Bytes
    if (*version != m_impl->version)
        return;
    offset += sizeof(*version);
    auto time = reinterpret_cast<const long long*>(data.constData() + offset); // 8 Bytes
    offset += sizeof(*time);
    auto isTx = reinterpret_cast<const bool*>(data.constData() + offset); // 1 Byte
    offset += sizeof(*isTx);
    auto currentPackNumber = reinterpret_cast<const int*>(data.constData() + offset); // 4 Bytes
    offset += sizeof(*currentPackNumber);
    auto totalPackNumber = reinterpret_cast<const int*>(data.constData() + offset); // 4 Bytes
    offset += sizeof(*totalPackNumber);
    auto addressSize = reinterpret_cast<const size_t*>(data.constData() + offset); // 8 Bytes
    if (data.size() < *addressSize + offset)
        return;
    offset += sizeof(*addressSize);
    auto address = std::string_view(data.constData() + offset, *addressSize);
    offset += *addressSize;
    auto dataSize = reinterpret_cast<const size_t*>(data.constData() + offset);
    if (data.size() < *dataSize + offset)
        return;
    offset += sizeof(*dataSize);
    auto datas = std::string_view(data.constData() + offset, *dataSize);

    if (*currentPackNumber == 0)
    {
        m_impl->traceData.time              = QDateTime::fromMSecsSinceEpoch(*time);
        m_impl->traceData.isTx              = *isTx;
        m_impl->traceData.currentPackNumber = *currentPackNumber;
        m_impl->traceData.totalPackNumber   = *totalPackNumber;
        m_impl->traceData.address           = QString::fromLatin1(address.data(), address.size());
        m_impl->traceData.data              = QString::fromLatin1(datas.data(), datas.size());
    }
    else
    {
        m_impl->traceData.currentPackNumber = *currentPackNumber;
        m_impl->traceData.data.append(QString::fromLatin1(datas.data(), datas.size()));
    }

    if (currentPackNumber == totalPackNumber - 1) // Last pack
    {
        emit dataArrived(m_impl->traceData.time, m_impl->traceData.isTx, m_impl->traceData.address, m_impl->traceData.data);
    }
}

void IOTraceService::run()
{
    m_impl->started = true;
    emit started();
    auto scope = qScopeGuard(
        [this]
        {
            m_impl->started = false;
            emit stoped();
        });
    m_impl->udp->bind(QHostAddress::LocalHost, m_impl->port);
    while (!m_impl->stopRequested)
    {
        if (m_impl->udp->hasPendingDatagrams())
        {
            parse(m_impl->udp->receiveDatagram().data());
        }
        else
        {
            QThread::msleep(100);
        }
    }
    m_impl->udp->close();
}

IOTraceService& IOTraceService::instance()
{
    static IOTraceService inst;
    return inst;
}

void IOTraceService::start()
{
    m_impl->stopRequested = false;
    QMetaObject::invokeMethod(this, &IOTraceService::run);
}

void IOTraceService::stop() { m_impl->stopRequested = true; }

void IOTraceService::setPort(unsigned short port) { m_impl->port = port; }

unsigned short IOTraceService::port() const { return m_impl->port; }

bool IOTraceService::isStarted() const { return m_impl->started; }

} // namespace IOTrace